#include <ncurses.h>
#include <signal.h>
#include "common.h"
#include "data.h"

enum {INIT, NONE, HELP, JOIN, USERLIST, SEND, ATTACKLIST} state = INIT;
enum {IDLE, CONNECTED, STARTED, FINISHED} gamestate = IDLE;

struct sockaddr_in server_addr;

char buf[MAX_MESSAGE]; // general purpose I/O buffer
int sock;
int game_timeout = -1;
int max_y, max_x;

int MY_UID;
Planet_t *PLANETS;
int PLANET_COUNT;
double SCALE[2];
int SIZES[4];
int PLAYER_COUNT;
char** PLAYERS;


WINDOW *help_button;
WINDOW *join_button;
WINDOW *send_button;
WINDOW *user_list;
WINDOW *join_message;
WINDOW *help_win;
WINDOW *send_command;
WINDOW *attack_list;
WINDOW *victor;

WINDOW *create_newwin(int height, int width, int starty, int startx);

void destroy_win(WINDOW *local_win);

int connectToServer(char *addr, int len);

int *join(char *username);
int *sendFleet(char *command);
void updateMap(void);
void drawPlanets(void);
void drawUserListWindow(WINDOW *win, int time_left);
void drawAttackListWindow();
int getUserList();

void printmsg(WINDOW *win, char *msg, bool border);

void eventLoop(int sig) {
    // reinstall handler
    alarm(1);
    signal(SIGALRM, eventLoop);
    if(game_timeout > 0) {
        game_timeout -= 1;
        state = USERLIST;
        getUserList();
        drawUserListWindow(user_list, 0);
        if(game_timeout==0){
            wclear(join_message);
            destroy_win(join_message);
        }
    }
    if(game_timeout == 0) {
        gamestate = STARTED;
    }
    if(gamestate == STARTED) {
        // update game map
        updateMap();
        drawPlanets();
        drawAttackListWindow();
    } else if(gamestate == CONNECTED) {
        // display time left
        drawUserListWindow(user_list, game_timeout);
    }
}

int main(int argc, char *argv[]) {
    int width, height;
    int ch;
    char address[20];

    initscr(); /* Start curses mode            */
    start_color();
    if(has_colors() == FALSE) {
        printf("\nno colors\n");
    }
    init_pair(1, COLOR_WHITE, COLOR_BLACK);
    init_pair(2, COLOR_RED, COLOR_BLACK);
    init_pair(3, COLOR_GREEN, COLOR_BLACK);
    init_pair(4, COLOR_YELLOW, COLOR_BLACK);
    init_pair(5, COLOR_BLUE, COLOR_BLACK);
    init_pair(6, COLOR_MAGENTA, COLOR_BLACK);
    init_pair(7, COLOR_CYAN, COLOR_BLACK);
    init_pair(8, COLOR_BLACK, COLOR_RED);
    init_pair(9, COLOR_BLACK, COLOR_GREEN);
    init_pair(10, COLOR_BLACK, COLOR_YELLOW);

    cbreak(); /* Line buffering disabled, Pass on everything to me  */
    keypad(stdscr, TRUE); /* I need that nifty F1         */

    getmaxyx(stdscr, max_y, max_x);

    height = 3;
    width = 10;
    printw("Press ESC to exit");
    refresh();
    help_button = create_newwin(height, width, 1, 2);
    printmsg(help_button, "F1 HELP", true);
    join_button = create_newwin(height, width, 1, 12);
    printmsg(join_button, "F2 JOIN", true);
    send_button = create_newwin(height, width, 1, 22);
    printmsg(send_button, "F4 SEND", true);
    user_list = create_newwin(3,15, 1, max_x-15);
    drawUserListWindow(user_list, game_timeout);
    attack_list = create_newwin(3,15, 1, 32);
    printmsg(attack_list, "F5 ATTACK",true);;
    while ((ch = getch()) != 27) {
        switch (ch) {
            case KEY_F(1):
                if(state == HELP) {
                    wclear(help_win);
                    destroy_win(help_win);
                    state = NONE;
                } else {
                    help_win = create_newwin(12, 40, (max_y-10)/2,(max_x-40)/2);
                    mvwprintw(help_win, 1, 1, "Hi! In this game you win by capturing");
                    mvwprintw(help_win, 2, 1, " all of the planets.");
                    mvwprintw(help_win, 3, 1, "To start, press F2, input server IP");
                    mvwprintw(help_win, 4, 1, " and desired username.");
                    mvwprintw(help_win, 5, 1, "To see dispatched fleets, press F5.");
                    mvwprintw(help_win, 6, 1, " List is in form FROM->TO COUNT ETA(s)");
                    mvwprintw(help_win, 7, 1, "To dispatch a fleet, press F4.");
                    mvwprintw(help_win, 8, 1, " Input format: FROM TO COUNT");
                    mvwprintw(help_win, 10, 1, "Good luck!");
                    wrefresh(help_win);
                    state = HELP;
                }
                break;
            case KEY_F(2):
                if(state == JOIN) {
                    wclear(join_message);
                    destroy_win(join_message);
                    state = NONE;
                } else {
                    join_message = create_newwin(4, 40, (max_y-4)/2, (max_x-40)/2);
                    printmsg(join_message, "Provide address:", true);
                    state = JOIN;
                    wgetnstr(join_message, address, 16);
                    if(address[0] == '\0' || address[0] == '\n') {
                        wclear(join_message);
                        destroy_win(join_message);
                        state = NONE;
                    } else {
                        int res = connectToServer(address, strlen(address));
                        char* msg = "Connecting...";
                        switch(res){
                            case 0:
                                printmsg(join_message, "Provide username:", true);
                                wgetnstr(join_message, address, 50);
                                int *results;
                                results = join(address);
                                if(results[0] != 0) {
                                    MY_UID = results[0];
                                    game_timeout = results[1];
                                    drawUserListWindow(user_list, game_timeout);
                                    sprintf(buf, "Connected with UID %i.", MY_UID);
                                    gamestate = CONNECTED;
                                    msg = buf;
                                    alarm(1);
                                    signal(SIGALRM, eventLoop);
                                } else {
                                    msg = "Could not register new user.";
                                }
                                free(results);
                                break;
                            case -1:
                                msg = "Could not create tcp socket.";
                                break;
                            case -2:
                                msg = "Could not connect to the server.";
                                break;
                            default:
                                printf("connectToServer code: %i\n", res);
                        }
                        printmsg(join_message, msg, false);
                    }
                }
                break;
            case KEY_F(3):
                if(gamestate == STARTED || gamestate == FINISHED){
                    if(state != USERLIST) {
                        state = USERLIST;
                        if(PLAYERS == NULL) {
                            getUserList();
                        }
                        drawUserListWindow(user_list, 0);
                    } else {
                        state = NONE;
                        wclear(user_list);
                        destroy_win(user_list);
                        user_list = create_newwin(3, 15, 1, max_x - 15);
                        drawUserListWindow(user_list, game_timeout);
                    }
                }
                break;
            case KEY_F(4):
                // create console for fleet sending command
                if(gamestate != STARTED) {
                    break;
                }
                if(state != SEND) {
                    state = SEND;
                    send_command = create_newwin(3, COLS, max_y - 3, 1);
                    printmsg(send_command, "Send [from to amount]:", true);
                    wgetnstr(join_message, address, 50);
                    if(address[0] == '\0' || address[0] == '\n') {
                        wclear(join_message);
                        destroy_win(join_message);
                        state = NONE;
                    } else {
                        int *results;
                        results = sendFleet(address);
                        sprintf(buf, "%i fleet sent. eta: %is", results[0], results[1]);
                        free(results);
                        printmsg(send_command, buf, false);
                        state = NONE;
                    }
                } else {
                    state = NONE;
                    wclear(send_command);
                    destroy_win(send_command);
                }
                break;
            case KEY_F(5):
                if(state!=ATTACKLIST){
                    state=ATTACKLIST;
                }else{
                    state=NONE;
                }
                break;
        }
    }
    endwin(); /* End curses mode                */
    return 0;
}

void drawAttackListWindow(){
    if(state==ATTACKLIST){
        char request[] = "A";
        char *buffer = buf;
        int a_count;
        char data_str[50];
        sendData(sock, request, strlen(request)+1);
        recv(sock, buf, MAX_MESSAGE, 0);
        sscanf(buf,"A %d %[^\n]",&a_count, buffer);
        if(a_count == 0){
            wclear(attack_list);
            destroy_win(attack_list);
            attack_list = create_newwin(3, 15, 1, 32);
            printmsg(attack_list, "No attacks", true);
        }else{
           wclear(attack_list);
           destroy_win(attack_list);
           attack_list = create_newwin(a_count+2, 15, 1, 32);
           int i=1;
           while(a_count--){
               int from;
               int to;
               int count;
               int time;
               int uid;
               wmove(attack_list, i, 1);
               sscanf(buffer, "%s", data_str);
               sscanf(data_str, "%d_%d_%d_%d_%d", &to, &count, &from, &uid, &time);
               wattron(attack_list, COLOR_PAIR(uid + 1));
               wprintw(attack_list, "%d", from);
               wattroff(attack_list, COLOR_PAIR(uid + 1));
               wprintw(attack_list, "->");
               wattron(attack_list, COLOR_PAIR(PLANETS[to].uid + 1));
               wprintw(attack_list, "%d", to);
               wattroff(attack_list, COLOR_PAIR(PLANETS[to].uid + 1));
               wprintw(attack_list, " %d %ds", count, time);
               int len = strlen(data_str);
               buffer += len + 1;
               i++;
           }
           wrefresh(attack_list);
        }
    } else {
        wclear(attack_list);
        destroy_win(attack_list);
        attack_list = create_newwin(3, 15, 1, 32);
        printmsg(attack_list, "F5 ATTACKS ",true);
    }
}

void drawUserListWindow(WINDOW *win, int time_left)
{
    if(time_left < 0){
        char *msg = "Not connected";
        printmsg(win, msg, true);
    } else if(time_left > 0){
        sprintf(buf, "Left: %d", time_left);
        printmsg(win, buf, true);
    } else if(PLAYER_COUNT){
        wclear(win);
        destroy_win(win);
        win = create_newwin(PLAYER_COUNT + 2, 15, 1, max_x - 15);
        int i;
        for(i=0; i < PLAYER_COUNT; i++){
            wattron(win, COLOR_PAIR(i + 2));
            mvwprintw(win, i + 1, 1, "%.13s", PLAYERS[i]);
        }
        wattroff(win, COLOR_PAIR(PLAYER_COUNT));
        wrefresh(win);
    }
}

int connectToServer(char *addr, int len)
{
     sock = socket(AF_INET, SOCK_STREAM, 0);
     if(sock == -1) {
           //  perror("could not create tcp socket");
             return -1;
     }
     server_addr.sin_family = AF_INET;
     server_addr.sin_port = htons(PORT);
     server_addr.sin_addr.s_addr = inet_addr(addr);

     if(connect(sock, (struct sockaddr *) &server_addr, sizeof(server_addr)) < 0) {
           // perror("tcp connect failed");
            return -2;
     }
     return 0;
}

int *join(char *username)
{
    int *res = malloc(sizeof(int) * 2);
    sprintf(buf, "J %s", username);
    sendData(sock, buf, strlen(buf) + 1);
    recv(sock, buf, MAX_MESSAGE, 0);
    sscanf(buf, "J %i %i", &res[0], &res[1]);
    return res;
}

int *sendFleet(char *cmd)
{
    int *res = malloc(sizeof(int) * 2);
    res[0] = 0;
    res[1] = 0;
    int from, to, amount;
    if(sscanf(cmd, "%i %i %i", &from, &to, &amount) != 3) {
        // incorrect number of parameters
        res[0] = -1;
        return res;
    }
    if(!((0 <= from) && (from < PLANET_COUNT))) {
        //planet does not exist
        res[0] = -2;
        return res;
    } else if(!((0 <= to) && (to < PLANET_COUNT))) {
        //planet does not exist
        res[0] = -3;
        return res;
    } else if(PLANETS[from].uid != MY_UID) {
        // player does not own requested planet
        res[0] = -4;
        return res;
    }
    sprintf(buf, "S %i %i %i", from, to, amount);
    sendData(sock, buf, strlen(buf) + 1);
    recv(sock, buf, MAX_MESSAGE, 0);
    sscanf(buf, "S %i %i", &amount, &to);
    res[0] = amount;
    res[1] = to;
    return res;
}

void updateMap(void)
{
    int planet_count;
    char planet_str[50];
    char *buffer = buf;
    bool init = false;
    int maxx = 0, maxy = 0, maxcap = 0;
    sendData(sock, "M", 2);
    recv(sock, buf, MAX_MESSAGE, 0);
    sscanf(buf, "M %i %[^\n]", &planet_count, buffer);
    if(PLANETS == NULL) {
        PLANETS = malloc(sizeof(Planet_t) * planet_count);
        PLANET_COUNT = planet_count;
        init = true;
    }
    int pid, x, y, uid, cap, sol;
    Planet_t *planet;
    while(planet_count--) {
        sscanf(buffer, "%s", planet_str);
        buffer += strlen(planet_str) + 1;
        sscanf(planet_str, "%i_%i_%i_%i_%i_%i",
            &pid, &x, &y, &uid, &cap, &sol);
        planet = &PLANETS[pid];
        if(init) {
            if(x > maxx) {
                maxx = x;
            }
            if(y > maxy) {
                maxy = y;
            }
            if(cap > maxcap) {
                maxcap = cap;
            }
            planet->pid = pid;
            planet->x = x;
            planet->y = y;
            planet->capacity = cap;
        }
        planet->uid = uid;
        planet->soliders = sol;
    }
    int winner;
    if((*(buffer-1) != 0) && (sscanf(buffer, "%i", &winner) == 1)) {
//        printw("player '%s'[%i] is the winner!", PLAYERS[winner - 1], winner);
        victor = create_newwin(3, 35, (max_y-3)/2, (max_x-35)/2);
        gamestate = FINISHED;
        wclear(victor);
        wmove(victor, 1, 1);
        wprintw(victor, "Player '%s' is the winner!", PLAYERS[winner-1]);
        wrefresh(victor);
        //gamestate = FINISHED;
    }
    if(init) {
        // calulate optimalmap scaling
        maxx *= 1.1;
        maxy *= 1.1;
        SCALE[0] = COLS / (double) maxx;
        SCALE[1] = LINES / (double) maxy;
        // calculate planet size classification
        SIZES[0] = (int) (maxcap * 0.2);
        SIZES[1] = (int) (maxcap * 0.4);
        SIZES[2] = (int) (maxcap * 0.6);
        SIZES[3] = (int) (maxcap * 0.8);
    }
}

void drawPlanets(void)
{
    int i;
    int x, y;
    char size;
    Planet_t planet;
    for(i = 0; i < PLANET_COUNT; i++) {
        planet = PLANETS[i];
        x = (int) (planet.x * SCALE[0]);
        y = (int) (planet.y * SCALE[1]);
        size = planet.capacity > SIZES[3] ? '@' : \
               planet.capacity > SIZES[2] ? 'O' : \
               planet.capacity > SIZES[1] ? 'o' : \
               planet.capacity > SIZES[0] ? '*' : \
               '.';
        move(y-1, x-2); printw("%4i", planet.soliders);
        move(y, x-2); printw("%i", planet.pid);
        attron(COLOR_PAIR(planet.uid + 1));
        mvaddch(y, x, size);
        attroff(COLOR_PAIR(planet.uid + 1));
    }
    refresh();
}

WINDOW *create_newwin(int height, int width, int starty, int startx)
{
    WINDOW *local_win;

    local_win = newwin(height, width, starty, startx);
    box(local_win, 0, 0); /* 0, 0 gives default characters
     * for the vertical and horizontal lines             */
    wrefresh(local_win); /* Show that box                */

    return local_win;
}

void destroy_win(WINDOW *local_win)
{
    wborder(local_win, ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ');
    wrefresh(local_win);
    delwin(local_win);
}

void printmsg(WINDOW *win, char *msg, bool border)
{
    wclear(win);
    if(border) {
        box(win, 0, 0);
    }
    wmove(win, 1, 1);
    wprintw(win, msg);
    wrefresh(win);
}

int getUserList() {
    char request[] = "U";
    char *buffer = buf;
    int p_count;
    char player_str[50];
    sendData(sock, request, strlen(request)+1);
    recv(sock, buf, MAX_MESSAGE, 0);
    sscanf(buf,"U %d %[^\n]",&PLAYER_COUNT, buffer);
    if(PLAYER_COUNT == 0){
        return -1;
    }else{
        p_count = PLAYER_COUNT;
        PLAYERS = malloc(sizeof(char*) * PLAYER_COUNT);
    }
    while(p_count--){
        int uid = 0;
        char name[50];
        sscanf(buffer, "%s", player_str);
        int len = strlen(player_str);
        buffer += len+1;
        sscanf(player_str, "%d_%s", &uid, name);
        PLAYERS[uid-1] = malloc(strlen(name) + 1);
        strcpy(PLAYERS[uid-1], name);
    }
    return 0;
}
